package com.m3958.logcollector.datacommons.controller;

import static org.springframework.http.HttpMethod.PUT;

import java.io.Serializable;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.auditing.AuditableBeanWrapperFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.data.rest.webmvc.RootResourceInformation;
import org.springframework.data.rest.webmvc.support.BackendId;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.rest.webmvc.support.ETag;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.m3958.logcollector.datacommons.exception.ErrorsToClient;
import com.m3958.logcollector.datacommons.repository.M3958DataRepository;
import com.m3958.logcollector.datacommons.util.AllParams;
import com.m3958.logcollector.datacommons.util.RepoUtils;

/**
 * almost copy from repositoryentity class in spring.
 * 
 * you cannot inherite repositoryentity, because it's protected scope.
 * 
 * @author jianglibo@gmail.com
 *
 */
@RepositoryRestController
public class Qcontroller extends AbstractQcontroller implements ApplicationEventPublisherAware {

    private static final String BASE_MAPPING = "/{repository}";

    static final String PROFILE_REL = "profile";

    @Autowired
    private ResourceMappings resourceMappings;

    @Autowired
    private Repositories repositories;

    private ConversionService conversionService;

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorsToClient> exp(Exception ex) throws Exception {
        ex.printStackTrace();
        throw ex;
    }

    @ExceptionHandler(JsonProcessingException.class)
    public ResponseEntity<ErrorsToClient> exp(JsonProcessingException ex) throws Exception {
        ErrorsToClient etc = new ErrorsToClient().addError("json", "wrong json format");
        return ResponseEntity.ok(etc);
    }

    @Autowired
    public Qcontroller(Repositories repositories, RepositoryRestConfiguration config, RepositoryEntityLinks entityLinks,
            PagedResourcesAssembler<Object> assembler, @Qualifier("defaultConversionService") ConversionService conversionService,
            AuditableBeanWrapperFactory auditableBeanWrapperFactory) {

        super(assembler, auditableBeanWrapperFactory, entityLinks, config);
        this.conversionService = conversionService;
    }

    @ResponseBody
    @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, params = { "!id", "q" })
    public Resources<?> getCollectionResource(final RootResourceInformation resourceInformation, //
            HttpServletRequest request, //
            DefaultedPageable pageable, //
            Sort sort, //
            @PathVariable String repository, //
            @RequestParam("q") String q, //
            PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {

        resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION);

        Map<String, String[]> allParams = request.getParameterMap();

        Object fq = (M3958DataRepository<?>) RepoUtils.findRepoByPath(repositories, resourceMappings, repository);

        if (fq == null || !(fq instanceof M3958DataRepository)) {
            throw new ResourceNotFoundException();
        }

        Iterable<?> results = ((M3958DataRepository<?>) fq).findQ(new AllParams(allParams), pageable.getPageable());
        ResourceMetadata metadata = resourceInformation.getResourceMetadata();
        Link baseLink = entityLinks.linkToPagedResource(resourceInformation.getDomainType(), pageable.isDefault() ? null : pageable.getPageable());

        Resources<?> result = toResources(results, assembler, metadata.getDomainType(), baseLink);
        result.add(getCollectionResourceLinks(resourceInformation, pageable));

        result.add(getCollectionResourceLinks(resourceInformation, pageable));
        return result;
    }

    @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, params = { "id" })
    public ResponseEntity<Resource<?>> getItemResource(RootResourceInformation resourceInformation, //
            @RequestParam("id") Serializable id, PersistentEntityResourceAssembler assembler, //
            @RequestHeader MultiValueMap<String, String> rawHeaders) throws HttpRequestMethodNotSupportedException {

        Object domainObj = getItemResource(resourceInformation, id);

        if (domainObj == null) {
            return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
        }

        return new ResponseEntity<Resource<?>>(assembler.toFullResource(domainObj), HttpStatus.OK);
    }

    private Object getItemResource(RootResourceInformation resourceInformation, Serializable id)
            throws HttpRequestMethodNotSupportedException, ResourceNotFoundException {

        resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM);

        return resourceInformation.getInvoker().invokeFindOne(id);
    }

    /**
     * PersistentEntityResourceAssemblerArgumentResolver起的作用就是转换。
     * 
     * @param resourceInformation
     * @param payload
     * @param id
     * @param assembler
     * @return
     * @throws HttpRequestMethodNotSupportedException
     * @throws JsonProcessingException
     */
    @RequestMapping(value = BASE_MAPPING + "/{id}/update", method = RequestMethod.PUT)
    public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInformation resourceInformation, PersistentEntityResource payload,
            @BackendId Serializable id, PersistentEntityResourceAssembler assembler, ETag eTag,
            @RequestHeader(value = ACCEPT_HEADER, required = false) String acceptHeader)
                    throws HttpRequestMethodNotSupportedException, JsonProcessingException {

        resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);

        // Force ID on unmarshalled object
        PersistentPropertyAccessor incomingWrapper = new ConvertingPropertyAccessor(payload.getPropertyAccessor(), conversionService);
        incomingWrapper.setProperty(payload.getPersistentEntity().getIdProperty(), id);

        RepositoryInvoker invoker = resourceInformation.getInvoker();
        Object objectToSave = incomingWrapper.getBean();

        Object domainObject = payload.getContent();

        eTag.verify(resourceInformation.getPersistentEntity(), domainObject);

        return payload.isNew() ? createAndReturn(objectToSave, invoker, assembler, getConfig().returnBodyOnCreate(acceptHeader))
                : saveAndReturn(objectToSave, invoker, PUT, assembler, getConfig().returnBodyOnUpdate(acceptHeader));

    }
}
